/*
*Copyright 2021-2023 NERCIS
*
*Licensed under the Apache License, Version 2.0 (the "License");
*you may not use this file except in compliance with the License.
*You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*Unless required by applicable law or agreed to in writing, software
*distributed under the License is distributed on an "AS IS" BASIS,
*WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*See the License for the specific language governing permissions and
*limitations under the License.
*/

package cn.ac.nercis.pes.service.utils;

import cn.ac.nercis.pes.common.constant.common.BaseConstants;
import cn.ac.nercis.pes.common.constant.evaluation.EvaluationConclusion;
import cn.ac.nercis.pes.common.constant.evaluation.EvaluationResult;
import cn.ac.nercis.pes.common.constant.evaluation.OffsetConclusion;
import cn.ac.nercis.pes.common.constant.knowledge.TargetType;
import cn.ac.nercis.pes.model.dto.evaluation.analyze.TargetScoreDTO;
import cn.ac.nercis.pes.model.dto.evaluation.onsite.EvaluationRecordDTO;
import cn.ac.nercis.pes.repository.model.evaluation.EvaluationRecordDO;
import cn.ac.nercis.pes.repository.model.evaluation.ResultCorrectionDO;
import cn.ac.nercis.pes.repository.model.project.SystemTargetDO;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * 计算辅助类
 * @author zpy
 * @version 1.0.0
 */
@Slf4j
public final class CalculateUtils {

    public static void calculateTechnologyScore(EvaluationRecordDTO evaluationRecord){
        if(!evaluationRecord.getIsApply()){
            evaluationRecord.setScore(null);
            evaluationRecord.setConclusion(EvaluationConclusion.NOT_APPLICABLE);
            return;
        }
        if(evaluationRecord.getDResult() == EvaluationResult.NOT_MATCH){
            evaluationRecord.setScore(0d);
            evaluationRecord.setConclusion(EvaluationConclusion.NOT_MATCH);
            return;
        }
        if(evaluationRecord.getDResult() == EvaluationResult.NOT_APPLICABLE){
            evaluationRecord.setScore(null);
            evaluationRecord.setConclusion(EvaluationConclusion.NOT_APPLICABLE);
            return;
        }
        if(evaluationRecord.getAResult() == EvaluationResult.MATCH
                && evaluationRecord.getKResult() == EvaluationResult.MATCH)
        {
            evaluationRecord.setScore(1d);
            evaluationRecord.setConclusion(EvaluationConclusion.MATCH);
        }else if(evaluationRecord.getAResult() == EvaluationResult.MATCH
                || evaluationRecord.getKResult() == EvaluationResult.MATCH){
            if(evaluationRecord.getAResult() == EvaluationResult.NOT_MATCH
                    && evaluationRecord.getACondition().getRevise() > 0){
                BigDecimal decimal = BigDecimal.valueOf(0.5 * evaluationRecord.getACondition().getRevise()).setScale(BaseConstants.SCORE_SCALE, RoundingMode.HALF_UP);
                evaluationRecord.setScore(decimal.doubleValue());
            }else if(evaluationRecord.getKResult() == EvaluationResult.NOT_MATCH
                    && evaluationRecord.getKCondition().getRevise() > 0){
                BigDecimal decimal = BigDecimal.valueOf(0.5 * evaluationRecord.getKCondition().getRevise()).setScale(BaseConstants.SCORE_SCALE, RoundingMode.HALF_UP);
                evaluationRecord.setScore(decimal.doubleValue());
            }else{
                evaluationRecord.setScore(0.5d);
            }
            evaluationRecord.setConclusion(EvaluationConclusion.PARTIALLY_MATCH);
        }else{
            if(evaluationRecord.getACondition().getRevise() > 0 && evaluationRecord.getKCondition().getResult()>0){
                BigDecimal decimal = BigDecimal.valueOf(0.25 * evaluationRecord.getACondition().getRevise() * evaluationRecord.getKCondition().getRevise())
                        .setScale(BaseConstants.SCORE_SCALE, RoundingMode.HALF_UP);
                evaluationRecord.setScore(decimal.doubleValue());
            }else if(evaluationRecord.getACondition().getRevise() > 0){
                BigDecimal decimal = BigDecimal.valueOf(0.25 * evaluationRecord.getACondition().getRevise())
                        .setScale(BaseConstants.SCORE_SCALE, RoundingMode.HALF_UP);
                evaluationRecord.setScore(decimal.doubleValue());
            }else if(evaluationRecord.getKCondition().getResult()>0){
                BigDecimal decimal = BigDecimal.valueOf(0.25 * evaluationRecord.getKCondition().getRevise())
                        .setScale(BaseConstants.SCORE_SCALE, RoundingMode.HALF_UP);
                evaluationRecord.setScore(decimal.doubleValue());
            }else{
                evaluationRecord.setScore(0.25d);
            }
            evaluationRecord.setConclusion(EvaluationConclusion.PARTIALLY_MATCH);
        }
    }

    public static void calculateManageScore(EvaluationRecordDTO evaluationRecord){
        if(!evaluationRecord.getIsApply()){
            evaluationRecord.setScore(null);
            evaluationRecord.setConclusion(EvaluationConclusion.NOT_APPLICABLE);
            return;
        }
        switch (evaluationRecord.getConclusion()){
            case MATCH:
                evaluationRecord.setScore(1d);
                break;
            case PARTIALLY_MATCH:
                evaluationRecord.setScore(0.5d);
                break;
            case NOT_APPLICABLE:
                evaluationRecord.setScore(null);
                break;
            default:
                evaluationRecord.setScore(0d);
                break;
        }
    }

    public static double getUnitScore(@NonNull List<EvaluationRecordDO> unitRecords){
        long count = unitRecords.stream()
                .filter(Objects::nonNull)
                .filter(r-> Objects.nonNull(r.getScore()) && r.getConclusion() != EvaluationConclusion.NOT_APPLICABLE)
                .count();
        if(count == 0){
            return 0;
        }
        double sum = unitRecords.stream()
                .filter(Objects::nonNull)
                .filter(r->Objects.nonNull(r.getScore())
                        && r.getConclusion() != EvaluationConclusion.NOT_APPLICABLE)
                .mapToDouble(EvaluationRecordDO::getScore)
                .sum();
        BigDecimal decimal = BigDecimal.valueOf(sum/count).setScale(BaseConstants.SCORE_SCALE, RoundingMode.HALF_UP);
        return decimal.doubleValue();
    }

    public static double getUnitScore(@NonNull List<EvaluationRecordDO> unitRecords, List<ResultCorrectionDO> resultCorrections){
        if(CollectionUtils.isEmpty(resultCorrections)){
            return getUnitScore(unitRecords);
        }
        long count = unitRecords.stream()
                .filter(Objects::nonNull)
                .filter(r-> Objects.nonNull(r.getScore()) && r.getConclusion() != EvaluationConclusion.NOT_APPLICABLE)
                .count();
        if(count == 0){
            return 0;
        }
        double sum = unitRecords.stream()
                .filter(Objects::nonNull)
                .filter(r->Objects.nonNull(r.getScore())
                        && r.getConclusion() != EvaluationConclusion.NOT_APPLICABLE)
                .mapToDouble(er->{
                    var rc = resultCorrections.stream()
                            .filter(r->StringUtils.equals(r.getRecord().getId(),er.getId()))
                            .findFirst()
                            .orElse(null);
                    return Objects.nonNull(rc) ? rc.getScore() : er.getScore();
                })
                .sum();
        BigDecimal decimal = BigDecimal.valueOf(sum/count).setScale(BaseConstants.SCORE_SCALE, RoundingMode.HALF_UP);
        return decimal.doubleValue();
    }

    public static double getLayerScore(List<EvaluationRecordDO> evalRecords, SystemTargetDO layerTarget, List<SystemTargetDO> systemTargets){
        var unitTargets = systemTargets.stream()
                .filter(st-> StringUtils.equals(st.getKmParentId(),layerTarget.getKmTargetId())
                        && st.getIsApply())
                .sorted(Comparator.comparingInt(SystemTargetDO::getKmSort))
                .toList();
        List<TargetScoreDTO> unitTargetScore = new ArrayList<>();
        for(var ut : unitTargets){
            var unitRecords = evalRecords.stream()
                    .filter(er->StringUtils.equals(er.getLayerTargetId(),layerTarget.getKmTargetId())
                            && StringUtils.equals(er.getUnitTargetId(),ut.getKmTargetId())
                            && Objects.nonNull(er.getScore())
                            && er.getConclusion() != EvaluationConclusion.NOT_APPLICABLE)
                    .collect(Collectors.toList());
            if(CollectionUtils.isNotEmpty(unitRecords)){
                unitTargetScore.add(TargetScoreDTO.builder()
                        .targetId(ut.getKmTargetId())
                        .targetType(ut.getKmType())
                        .targetName(ut.getKmName())
                        .targetScore(ut.getKmScore())
                        .evalScore(getUnitScore(unitRecords))
                        .build());
            }
        }
        if(CollectionUtils.isEmpty(unitTargetScore)){
            return BaseConstants.LAYER_NOT_APPLY_SCORE;
        }
        double sum = unitTargetScore.stream()
                .mapToDouble(uts->uts.getEvalScore()*uts.getTargetScore())
                .sum();
        double count = unitTargetScore.stream()
                .mapToDouble(TargetScoreDTO::getTargetScore)
                .sum();
        if(count==0){
            return 0;
        }
        BigDecimal decimal = BigDecimal.valueOf(sum/count).setScale(BaseConstants.SCORE_SCALE, RoundingMode.HALF_UP);
        return decimal.doubleValue();
    }

    public static double getLayerScore(List<EvaluationRecordDO> evalRecords,List<ResultCorrectionDO> resultCorrections,
                                       SystemTargetDO layerTarget, List<SystemTargetDO> systemTargets){
        var unitTargets = systemTargets.stream()
                .filter(st-> StringUtils.equals(st.getKmParentId(),layerTarget.getKmTargetId())
                        && st.getIsApply())
                .sorted(Comparator.comparingInt(SystemTargetDO::getKmSort))
                .toList();
        List<TargetScoreDTO> unitTargetScore = new ArrayList<>();
        for(var ut : unitTargets){
            var unitRecords = evalRecords.stream()
                    .filter(er->StringUtils.equals(er.getLayerTargetId(),layerTarget.getKmTargetId())
                            && StringUtils.equals(er.getUnitTargetId(),ut.getKmTargetId())
                            && Objects.nonNull(er.getScore())
                            && er.getConclusion() != EvaluationConclusion.NOT_APPLICABLE)
                    .collect(Collectors.toList());
            var unitResultCorrections = resultCorrections.stream()
                    .filter(rc-> StringUtils.equals(rc.getLayerTargetId(),layerTarget.getKmTargetId())
                            && StringUtils.equals(rc.getUnitTargetId(),ut.getKmTargetId())
                            && Objects.nonNull(rc.getScore()))
                    .toList();
            if(CollectionUtils.isNotEmpty(unitRecords)){
                unitTargetScore.add(TargetScoreDTO.builder()
                        .targetId(ut.getKmTargetId())
                        .targetType(ut.getKmType())
                        .targetName(ut.getKmName())
                        .targetScore(ut.getKmScore())
                        .evalScore(getUnitScore(unitRecords,unitResultCorrections))
                        .build());
            }
        }
        if(CollectionUtils.isEmpty(unitTargetScore)){
            return BaseConstants.LAYER_NOT_APPLY_SCORE;
        }
        double sum = unitTargetScore.stream()
                .mapToDouble(uts->uts.getEvalScore()*uts.getTargetScore())
                .sum();
        double count = unitTargetScore.stream()
                .mapToDouble(TargetScoreDTO::getTargetScore)
                .sum();
        if(count==0){
            return 0;
        }
        BigDecimal decimal = BigDecimal.valueOf(sum/count).setScale(BaseConstants.SCORE_SCALE, RoundingMode.HALF_UP);
        return decimal.doubleValue();
    }

    public static double getOverallScore(List<TargetScoreDTO> layerScores){
        double technologyScore = getTechnologyScore(layerScores);
        double managementScore = getManagementScore(layerScores);
        BigDecimal decimal = BigDecimal.valueOf(technologyScore+managementScore).setScale(BaseConstants.OVERALL_SCORE_SCALE, RoundingMode.HALF_UP);
        return decimal.doubleValue();
    }

    private static double getTechnologyScore(List<TargetScoreDTO> layerScores){
        double sum = layerScores.stream()
                .filter(ls->ls.getEvalScore() > BaseConstants.LAYER_NOT_APPLY_SCORE
                        && ls.getTargetType() == TargetType.TECHNOLOGY)
                .mapToDouble(uts->uts.getEvalScore()*uts.getTargetScore())
                .sum();
        double count = layerScores.stream()
                .filter(ls->ls.getEvalScore() > BaseConstants.LAYER_NOT_APPLY_SCORE
                        && ls.getTargetType() == TargetType.TECHNOLOGY)
                .mapToDouble(TargetScoreDTO::getTargetScore)
                .sum();
        if(count==0){
            return 0;
        }
        return (sum/count)*BaseConstants.TECHNOLOGY_TARGET_SCORE;
    }

    private static double getManagementScore(List<TargetScoreDTO> layerScores){
        double sum = layerScores.stream()
                .filter(ls->ls.getEvalScore() > BaseConstants.LAYER_NOT_APPLY_SCORE
                        && ls.getTargetType() == TargetType.MANAGEMENT)
                .mapToDouble(uts->uts.getEvalScore()*uts.getTargetScore())
                .sum();
        double count = layerScores.stream()
                .filter(ls->ls.getEvalScore() > BaseConstants.LAYER_NOT_APPLY_SCORE
                        && ls.getTargetType() == TargetType.MANAGEMENT)
                .mapToDouble(TargetScoreDTO::getTargetScore)
                .sum();
        if(count==0){
            return 0;
        }
        return (sum/count)*BaseConstants.MANAGEMENT_TARGET_SCORE;
    }

    /**
     * 获取弥补后分值
     * @param paScore 弥补对象分值
     * @param pbScore 被弥补对象分值
     * @return 返回弥补后计算出的分值
     */
    public static double getOffsetScore(@NonNull Double paScore,@NonNull Double pbScore){
        return Math.max(0.5 * paScore,pbScore);
    }

    public static EvaluationConclusion getUnitEvalConclusion(List<EvaluationRecordDO> unitRecords){
        //判断是否全为不适用
        if(unitRecords.size() == getRecordConclusionCount(unitRecords,EvaluationConclusion.NOT_APPLICABLE)){
            return EvaluationConclusion.NOT_APPLICABLE;
        }
        //从列表中排除不适用
        List<EvaluationRecordDO> records = unitRecords.stream()
                .filter(er->er.getConclusion()!=EvaluationConclusion.NOT_APPLICABLE)
                .collect(Collectors.toList());
        long count = records.size();
        if(count == getRecordConclusionCount(records,EvaluationConclusion.MATCH)){
            return EvaluationConclusion.MATCH;
        }else if(count == getRecordConclusionCount(records,EvaluationConclusion.NOT_MATCH)){
            return EvaluationConclusion.NOT_MATCH;
        }else{
            return EvaluationConclusion.PARTIALLY_MATCH;
        }
    }

    public static Double calculateResultCorrection(OffsetConclusion conclusion){
        return switch (conclusion) {
            case MATCH -> 1d;
            case PARTIALLY_MATCH -> 0.5d;
            default -> null;
        };
    }

    private static long getRecordConclusionCount(List<EvaluationRecordDO> records,EvaluationConclusion conclusion){
        return  records.stream()
                .filter(er->er.getConclusion() == conclusion)
                .count();
    }
}